#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>

#include <fcntl.h>
#include <errno.h>
#include <const.h>
#include <sys/stat.h>

#define MAY_EXEC 1
#define MAY_WRITE 2
#define MAY_READ 4

static int permission(struct m_inode *inode, int mask)
{
    int mode = inode->i_mode;

/* special case: not even root can read/write a deleted file */
    if (inode->i_dev && !inode->i_nlinks)
        return 0;
    else if (current->euid == inode->i_uid)
        mode >>= 6;
    else if (current->egid == inode->i_gid)
        mode >>= 3;
    if (((mode & mask & 0007) == mask) || suser())
        return 1;
    return 0;
}

static int match(int len, const char *name, struct dir_entry *de)
{
    register int same;

    if (!de || !de->inode || len > NAME_LEN)
        return 0;
    if (len < NAME_LEN && de->name[len])
        return 0;
    __asm__("cld\n\t"
        "fs\n\trepe\n\tcmpsb\n\t"
        "setz %%al"
        :"=a"(same)
        :"0"(0), "S"((long) name), "D"((long) de->name), "c"(len)
        );
    return same;
}

static struct buffer_head *find_entry(struct m_inode **dir,
    const char *name, int namelen, struct dir_entry **res_dir)
{
    int entries;
    int block, i;
    struct buffer_head *bh;
    struct dir_entry *de;
    struct super_block *sb;

#ifdef NO_TRUNCATE
    if (namelen > NAME_LEN)
        return NULL;
#else
    if (namelen > NAME_LEN)
        namelen = NAME_LEN;
#endif
    entries = (*dir)->i_size / (sizeof (struct dir_entry));
    *res_dir = NULL;
    if (!namelen)
        return NULL;
/* check for '..', as we might have to do some "magic" for it */
    if (namelen == 2 && get_fs_byte(name) == '.' && get_fs_byte(name + 1) == '.') {
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
        if ((*dir) == current->root)
            namelen = 1;
        else if ((*dir)->i_num == ROOT_INO) {
/* '..' over a mount-point results in 'dir' being exchanged for the mounted
   directory-inode. NOTE! We set mounted, so that we can iput the new dir */
            sb = get_super((*dir)->i_dev);
        }
    }
    block = (*dir)->i_zone[0];
    if (!block)
        return NULL;
    bh = bread((*dir)->i_dev, block);
    if (!bh)
        return NULL;
    i = 0;
    de = (struct dir_entry *) bh->b_data;
    while (i < entries) {
        if (match(namelen, name, de)) {
            *res_dir = de;
            return bh;
        }
        de++;
        i++;
    }
    return NULL;
}

static struct m_inode *get_dir(const char *pathname)
{
    char c;
    const char *thisname;
    struct m_inode *inode;
    struct buffer_head *bh;
    int namelen, inr, idev;
    struct dir_entry *de;

    if (!current->root || !current->root->i_count)
        panic("No root inode");
    if (!current->pwd || !current->pwd->i_count)
        panic("No cwd inode");
    c = get_fs_byte(pathname);
    if (c == '/') { // 第一个字符是/，说明是绝对路径
        inode = current->root;
        pathname++;
    } else if (c)   // 相对路径
        inode = current->pwd;
    else
        return NULL;
    inode->i_count++;
    while (1) {
        int i;
        thisname = pathname;
        if (!S_ISDIR(inode->i_mode) || !permission(inode, MAY_EXEC)) {
            return NULL;
        }
        c = get_fs_byte(pathname++);
        for(namelen = 0; c && (c != '/'); namelen++)
            c = get_fs_byte(pathname++);
        if (!c)
            return inode;
        for (i = 0; i < namelen; i++)
            printk("%c", thisname[i]);
        printk("\r\n");
        bh = find_entry(&inode, thisname, namelen, &de);
        if (!bh) {
            return NULL;
        }
        inr = de->inode;
        idev = inode->i_dev;
        inode = iget(idev, inr);
        if (!inode)
            return NULL;
    }
}

/**
 * 通过路径名找到文件名、文件所在目录的i节点，计算出文件名长度
 * @param pathname 文件的路径（绝对路径或相对路径）
 * @param namelen 通过指针得到文件名的长度
 * @param name 通过指针得到文件名
 * @return NULL：没有找到文件的目录；其他：文件目录的i节点
 */
static struct m_inode *dir_namei(const char *pathname, int *namelen, const char **name)
{
    char c;
    const char *basename;
    struct m_inode *dir;

    dir = get_dir(pathname);
    if (!dir)
        return NULL;
    basename = pathname;
    while (c = get_fs_byte(pathname++))
        if (c == '/')
            basename = pathname;
    *namelen = pathname - basename - 1; // 计算文件名长度
    *name = basename;   // 计算文件名起始地址
    printk("filename: %s\n\r", basename);
    return dir;
}

int open_namei(const char *pathname)
{
    const char *basename;
    int namelen;
    struct m_inode *dir;

    dir = dir_namei(pathname, &namelen, &basename);
    if (!dir)
        return -ENOENT;
    return 0;
}